
#include "./base-net-server.h"

namespace fasmio { namespace net_server {

const unsigned int kNetServerConnFiberCount = 10;

BaseNetServer::BaseNetServer(ILogger* logger, const char* module_name, unsigned short port) :
    logger_(logger),
    module_name_(module_name),
    port_(port),
    mlogger_(logger, module_name),
    container_(0),
    listen_sock_(),
    listen_fiber_(0),
    conn_queue_(),
    conn_processor_(logger, "netsvr-conn-fiber")
{
}

BaseNetServer::~BaseNetServer()
{
}

bool BaseNetServer::Start(IContainer* container)
{
    if (container == nullptr)
        return false;
    container_ = container;

    mlogger_.Info("Starting %s ...", module_name_);

    listen_sock_.reset(fiber_env::NewTCPSocket());

    const char* listen_ip = "0.0.0.0";
    const unsigned short listen_port = port_;
    if (!listen_sock_->Bind(listen_ip, listen_port, true))
    {
        mlogger_.Error("Failed to bind %s:%d", listen_ip, listen_port);
        return false;
    }

    if (!listen_sock_->Listen(5))
        return false;
    mlogger_.Info("Listening at %s:%d", listen_ip, listen_port);

    if (nullptr == (listen_fiber_ = fiber_env::CreateFiber(
                    listen_fiber, this, "netsvr-listen-fiber")))
        return false;

    if (!conn_processor_.Start(kNetServerConnFiberCount,
                    conn_queue_, this, &BaseNetServer::conn_fiber))
        return false;
    mlogger_.Info("%d connection fibers started", kNetServerConnFiberCount);

    mlogger_.Info("%s started", module_name_);
    return true;
}

void BaseNetServer::Stop()
{
    mlogger_.Info("Stopping %s ...", module_name_);

    // // TODO: how to interrupt the accept call??
    // // listen_sock_->Shutdown(SHUT_RDWR);
    // // listen_sock_->Close();
    // listen_fiber_->Join();
    // listen_fiber_ = 0;
    // listen_sock_.reset();

    conn_processor_.Stop();

    CleanupUnhandledConn();

    container_ = 0;
    mlogger_.Info("%s stopped", module_name_);
}

int BaseNetServer::listen_fiber(void* arg)
{
    BaseNetServer *server = reinterpret_cast<BaseNetServer*>(arg);
    return server->listen_fiber();
}

int BaseNetServer::listen_fiber()
{
    if (listen_sock_.get() == 0)
        return 1;

    while (true)
    {
        char addr[256];
        fiber_env::ITCPSocket *sock = listen_sock_->Accept(addr, sizeof(addr));
        if (sock == 0)
            break;

        mlogger_.Verbose("Incoming connection from %s", addr);
        INetServerConnHandler *handler = CreateHandler(&mlogger_, sock, addr);
        if (handler == 0)
            delete sock;
        else
        {
            // Now the handler object hold the socket and responsible
            // the deletion of it.
            // handler object will be deleted once the connection is finished.
            conn_queue_.Push(handler);
        }
    }

    return 0;
}

void BaseNetServer::conn_fiber(INetServerConnHandler *handler)
{
    if (nullptr != handler)
    {
        handler->HandleConnection();
        delete handler;
    }
}

void BaseNetServer::CleanupUnhandledConn()
{
    unsigned int unhandled = 0;
    while (true)
    {
        INetServerConnHandler *handler = conn_queue_.Pop(false);
        if (handler == nullptr)
            break;

        delete handler;
        conn_queue_.TaskDone();
        ++unhandled;
    }

    if (unhandled > 0)
        mlogger_.Info("%d unhandled connections dropped", unhandled);
}

bool BaseNetServer::PrepareRequest(INetRequest *request)
{
    return container_->PrepareRequest(request);
}

bool BaseNetServer::ServeRequest(INetRequest *request, INetResponse **response)
{
    return container_->ServeRequest(request, response);
}

}}  // namespace fasmio::net_server

